package crypto;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;

/**
 * Clase de utilidades para guardar y cargar las llaves a partir de archivos con extension .key
 */
public class KeyUtils 
{
	/**
	 * Guarda una llave con el nombre especificado
	 * 
	 * @param key el arreglo de bytes que representa la llave
	 * @param nombre el nombre deseado para la llave
	 * @throws IOException si no se puede guardar la llave
	 */
	public static void saveKeyToFile(byte[] key, String nombre) throws IOException 
	{
		FileOutputStream out = new FileOutputStream("./data/" + nombre + ".key");
		try 
		{
			out.write(key);
		}
		catch (Exception e) 
		{
			throw new IOException("Unexpected error", e);
		} 
		finally 
		{
			out.close();
		}
	}

	/**
	 * Guarda una llave (DSA) dando las especificaciones de esta
	 * 
	 * @param p parte P de la llave
	 * @param q parte Q de la llave
	 * @param g parte G de la llave
	 * @param nombre el nombre deseado para la llave
	 * 
	 * @throws IOException si no se puede guardar la llave
	 */
	public static void saveDSAKeyToFile(BigInteger xy, BigInteger p,BigInteger q, BigInteger g, String nombre) throws IOException
	{
		ObjectOutputStream oout = new ObjectOutputStream(
				new BufferedOutputStream(new FileOutputStream("./data/" + nombre + ".key")));
		try
		{
			oout.writeObject(xy);
			oout.writeObject(p);
			oout.writeObject(q);
			oout.writeObject(g);
		}
		catch (Exception e)
		{
			throw new IOException("Unexpected error", e);
		}
		finally
		{
			oout.close();
		}
	}
	
	/**
	 * Lee una llave a partir de su ubicacion
	 * 
	 * @param rutaLlave la ruta de la llave
	 * @return un arreglo de bytes que representa la llave
	 * 
	 * @throws IOException si no se puede cargar la llave
	 */
	public static byte[] readKey(String rutaLlave) throws IOException 
	{
		File file = new File(rutaLlave);
		InputStream is = new FileInputStream(file);

		long length = file.length();

		if (length > Integer.MAX_VALUE) {
			System.out.println("Archivo demasiado grande");
			return null;
		}

		byte[] bytes = new byte[(int)length];

		int offset = 0;
		int numRead = 0;
		while ( (offset < bytes.length)
				&&
				( (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) ) {

			offset += numRead;

		}

		if (offset < bytes.length) {
			throw new IOException("No se pudo leer el archivo completamente " + file.getName());
		}

		is.close();
		return bytes;
	}
	
	/**
	 * lee una llave privada (DSA) a partir de su ubicacion
	 * 
	 * @param rutaLlave la ubicacion de la llave
	 * @return una llave privada dada por la especificacion DSA
	 * 
	 * @throws IOException si no se puede leer la llave
	 */
	public static PrivateKey loadDSAPrivateKey(String rutaLlave) throws IOException
	{
		ObjectInputStream oin = new ObjectInputStream(new BufferedInputStream(new FileInputStream(rutaLlave)));
		try {
			
			BigInteger x = (BigInteger) oin.readObject();
			BigInteger p = (BigInteger) oin.readObject();
			BigInteger q = (BigInteger) oin.readObject();
			BigInteger g = (BigInteger) oin.readObject();

			KeyFactory fact = KeyFactory.getInstance("DSA");
			DSAPrivateKeySpec spec = new DSAPrivateKeySpec(x, p, q, g);

			PrivateKey key = fact.generatePrivate(spec);
			return key;
		} 
		catch (Exception e) 
		{
			throw new RuntimeException("Spurious serialisation error", e);
		}
		finally 
		{
			oin.close();
		}
	}

	/**
	 * lee una llave publica (DSA) a partir de su ubicacion
	 * 
	 * @param rutaLlave la ubicacion de la llave
	 * @return una llave publica dada por la especificacion DSA
	 * 
	 * @throws IOException si no se puede leer la llave
	 */
	public static PublicKey loadDSAPublicKey(InputStream in) throws IOException
	{
		ObjectInputStream oin = new ObjectInputStream(new BufferedInputStream(in));
		PublicKey key = null;
		
		try {
			BigInteger y = (BigInteger) oin.readObject();
			BigInteger p = (BigInteger) oin.readObject();
			BigInteger q = (BigInteger) oin.readObject();
			BigInteger g = (BigInteger) oin.readObject();

			KeyFactory fact = KeyFactory.getInstance("DSA");
			
			DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g);
			
			key = fact.generatePublic(spec);
			return key;
		} 
		catch (ClassNotFoundException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (NoSuchAlgorithmException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (InvalidKeySpecException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		finally 
		{
			oin.close();
		}
		return key;
	}
}
